using System.Drawing;
using System.Xml;
using Wilco.SyntaxHighlighting.Engine.Highlighter;
using Wilco.SyntaxHighlighting.Engine.Node;
using Wilco.SyntaxHighlighting.Engine.Node.Implementation.ASPX;
using Wilco.SyntaxHighlighting.Engine.Node.Implementation.Xml;
using Wilco.SyntaxHighlighting.Engine.Node.Occurrence;
using Wilco.SyntaxHighlighting.Engine.Parser;
using Wilco.SyntaxHighlighting.Engine.Scanner;
using Wilco.SyntaxHighlighting.Engine.Tokenizer;

namespace Wilco.SyntaxHighlighting.Engine.Scanner.Implementation.ASPX
{
    /// <summary>
    /// Represents an ASPX scanner.
    /// </summary>
    public class ASPXScanner : ScannerBase
    {
        private ASPXNode aspxNode;
        private readonly XmlScanner xmlScanner;
        private string language;
        private readonly IParser parser;

        /// <summary>
        /// Gets or sets the script node.
        /// </summary>
        public ASPXNode ASPXNode
        {
            get
            {
                return aspxNode;
            }
            set
            {
                if (value != aspxNode)
                {
                    aspxNode = value;
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="ASPXScanner"/> class.
        /// </summary>
        public ASPXScanner() : this(null, null)
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="ASPXScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        public ASPXScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult) : this(tokenizer, scannerResult, new ASPXNode())
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="ASPXScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <param name="aspxNode">A <see cref="Node.Implementation.ASPX.ASPXNode"/> object.</param>
        public ASPXScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult, ASPXNode aspxNode) : base(tokenizer, scannerResult)
        {
            this.aspxNode = aspxNode;
            this.aspxNode.BackColor = Color.Yellow;
            xmlScanner = new XmlScanner(Tokenizer, ScannerResult);
            xmlScanner.Match += this.xmlScanner_Match;
            parser = new HtmlParser();
            SetID("ASPXScanner");
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="IScanner"/> implementation class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <returns>A new instance of a <see cref="IScanner"/> implementation class.</returns>
        public override IScanner Create(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            return new ASPXScanner(tokenizer, scannerResult);
        }

        /// <summary>
        /// Loads the state of the scanner.
        /// </summary>
        /// <param name="state">An <see cref="System.Object"/> that contains the state of the scanner.</param>
        public override void LoadState(object state)
        {
            XmlElement element = (XmlElement)state;
			
            // Load settings.
            FontConverter converter = new FontConverter();
            aspxNode.BackColor = ColorTranslator.FromHtml(element.SelectSingleNode("settings/setting[@name='BackColor']").InnerText);
            aspxNode.ForeColor = ColorTranslator.FromHtml(element.SelectSingleNode("settings/setting[@name='ForeColor']").InnerText);
            aspxNode.Font = (Font)converter.ConvertFromString(element.SelectSingleNode("settings/setting[@name='Font']").InnerText);
            aspxNode.NavigateUrl = element.SelectSingleNode("settings/setting[@name='NavigateUrl']").InnerText;
        }

        /// <summary>
        /// Saves the current state of the scanner.
        /// </summary>
        /// <param name="container">The container which will contain the state.</param>
        /// <returns>An <see cref="System.Object"/> that contains the state of the scanner.</returns>
        public override object SaveState(object container)
        {
            XmlDocument document = (XmlDocument)container;
            XmlElement element = (XmlElement)base.SaveState(container);
			
            // Save settings.
            XmlElement settingRootElement = document.CreateElement("settings");
            element.AppendChild(settingRootElement);

            FontConverter converter = new FontConverter();
            settingRootElement.AppendChild(CreateSetting(document, "BackColor", ColorTranslator.ToHtml(aspxNode.BackColor)));
            settingRootElement.AppendChild(CreateSetting(document, "ForeColor", ColorTranslator.ToHtml(aspxNode.ForeColor)));
            settingRootElement.AppendChild(CreateSetting(document, "ForeColor", converter.ConvertToString(aspxNode.Font)));
            settingRootElement.AppendChild(CreateSetting(document, "NavigateUrl", aspxNode.NavigateUrl));

            return element;
        }

        /// <summary>
        /// Creates a steting.
        /// </summary>
        /// <param name="document">The document which will contain the setting.</param>
        /// <param name="name">The name of the setting.</param>
        /// <param name="value">The value of the setting.</param>
        /// <returns>The <see cref="System.Xml.XmlElement"/> which represents the setting.</returns>
        private static XmlElement CreateSetting(XmlDocument document, string name, string value)
        {
            XmlElement element = document.CreateElement("setting");
            element.SetAttribute("name", name);
            element.InnerText = value;
            return element;
        }

        /// <summary>
        /// Scans a token.
        /// </summary>
        /// <remarks>
        /// An <see cref="IScanner"/> implementation will generally have a reference to a 
        /// <see cref="NodeCollection"/> which will be used to store results of a scan.
        /// </remarks>
        /// <param name="token">A token from the source code.</param>
        public override void Scan(string token)
        {
            if (!Enabled)
            {
                if (Child != null)
                {
                    Child.Scan(token);
                }
            }
            else
            {
                // Highlight as XML until a script block was found.
                if (language == null)
                {
                    bool handled = false;
                    if ((Tokenizer.Position + 3 <= Tokenizer.Source.Length) && Tokenizer.GetNextTokens(3) == "<%@")
                    {
                        int charIndex = Tokenizer.Source.IndexOf("%>", Tokenizer.Position + 3);
                        if (charIndex == -1)
                            charIndex = Tokenizer.Source.Length;
                        else
                            charIndex += 2;

                        ScannerResult.Add(new Occurrence(Tokenizer.Position, charIndex - Tokenizer.Position, aspxNode));

                        Tokenizer.MoveTo(charIndex);
                        handled = true;
                    }
                    else if (Tokenizer.Position + 2 <= Tokenizer.Source.Length)
                    {
                        string right = Tokenizer.GetNextTokens(2);
                        if (right == "<%" || right == "%>")
                        {
                            ScannerResult.Add(new Occurrence(Tokenizer.Position, 2, aspxNode));
                            handled = true;
                        }
                    }

                    if (!handled)
                    {
                        // Find script block.
                        xmlScanner.Scan(token);
                    }
                }
                else
                {
                    if ((Tokenizer.Position + 9) <= Tokenizer.Source.Length && Tokenizer.GetNextTokens(9) == "</script>")
                    {
                        language = null;
                        xmlScanner.Scan(token);
                    }
                    else if (Child != null)
                    {
                        Child.Scan(token);
                    }
                }
            }
        }

        /// <summary>
        /// Handles the match event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void xmlScanner_Match(object sender, MatchEventArgs e)
        {
            if (e.Tag != null && e.Tag.ToLower() == "script")
            {
                if (e.Type == MatchType.StartTag)
                {
                    language = e.Attributes["lang"];
                    if (language == null)
                        language = e.Attributes["language"];

                    if (language != null)
                    {
                        language = language.Replace("\"", "").Replace("'", "");
                        if (language.Length == 0)
                        {
                            language = null;
                        }
                    }

                    if (language != null)
                    {
                        HighlighterBase highlighter = Register.Instance.Highlighters[language];
                        if (highlighter != null)
                        {
                            highlighter = highlighter.Create();
                            highlighter.Parser = parser;
                            Child = highlighter.BuildEntryPointScanner(Tokenizer, ScannerResult);
                        }
                        else
                            language = null;
                    }
                }
                else if (e.Type == MatchType.EndTag)
                    language = null;
            }
        }

        /// <summary>
        /// Resets the scanner.
        /// </summary>
        public override void Reset()
        {
            language = null;
        }
    }
}